<script setup lang="ts">
import { computed, nextTick, reactive, ref, useAttrs, watch } from 'vue';
import { genKey, isBool, isFunc, KeyValues, OptionalMutable, PageParams, TableColumnProps, treeUtils } from '../../utils';
import { ElMessage, ElMessageBox, FormProps, PaginationProps, TableProps } from 'element-plus';
import UiSearchForm from './search-form.vue'
import UiTableColumn from './table-column.vue'

const props = defineProps<{
  columns?: TableColumnProps<any>[],
  fetchAction: <T extends KeyValues<any>>(params: T & PageParams) => Promise<{ data: any[], total?: number }>,
  config?: {
    index?: boolean,
    selection?: boolean,
    pager?: boolean | OptionalMutable<PaginationProps>
  },
  formProps?: OptionalMutable<FormProps>,
  expandAll?: boolean
}>()

const cls = 'ui-data-view'

const listData = reactive({
  total: 0,
  data: [],
  form: {},
  params: { pageNum: 1, pageSize: 10 }
})

const allColumns = computed(() => {
  const hasIndex = props.config?.index !== false
  const hasSelection = !!props.config?.selection

  const columns = ([
    hasSelection && { type: 'selection', width: 55, align: 'left' },
    hasIndex && {
      label: '序号',
      type: 'index',
      width: 60,
      render: (h, { row, $index }) => h('span', row._index || ($index + 1))
    },
    ...(props.columns || [])
  ] as TableColumnProps<any>[]).filter(_ => _)

  const selectionCol = columns.find(_ => _.type === 'selection')
  if (selectionCol) {
    columns.splice(columns.indexOf(selectionCol), 1)
    columns.unshift(selectionCol)
  }

  return treeUtils.map({
    data: columns,
    cb: node => {
      const obj: TableColumnProps<any> = node.showOverflowTooltip === false
      ? { align: 'left', headerAlign: 'center' }
      : {}
      return {
        minWidth: 100,
        align: 'center',
        showOverflowTooltip: true,
        ...obj,
        ...node,
        _k: genKey()
      }
    }
  })
})

const form = ref<KeyValues<any>>({})
const initForm = ref<KeyValues<any>>({})

const setInitForm = async <T extends KeyValues<any>>(formSource: T | (() => any)) => {
  if (isFunc(formSource)) {
    initForm.value = await (formSource as Function)()
  } else {
    initForm.value = { ...formSource }
  }
  form.value = { ...initForm.value }
  listData.form = { ...initForm.value }
}

const loading = ref(false)
const selectedRows = ref<any[]>([])
const attrs = useAttrs()

const tableProps = computed<TableProps<any>>(() => {
  return {
    ...attrs,
    data: listData.data,
    defaultExpandAll: props.expandAll
  }
})

const onSelectionChange = (selection: any[]) => {
  selectedRows.value = selection
}

const getData = async (params: PageParams = {}) => {
  loading.value = true
  selectedRows.value = []
  listData.params = { ...listData.params, ...params }
  try {
    const res = await props.fetchAction({ ...listData.form, ...listData.params })
    ;(listData.data as any[]) = res.data || []
    listData.total = res.total || 0
  } catch (e) {
    listData.data = []
    listData.total = 0
  } finally {
    loading.value = false
  }
}

const pagerProps = computed<OptionalMutable<PaginationProps>>(() => {
  const pagerOptions = props.config?.pager
  return {
    background: true,
    pageSizes: [10, 20, 50],
    layout: 'total, sizes, prev, pager, next, jumper',
    ...(!pagerOptions || isBool(pagerOptions) ? {} : pagerOptions),
    pageSize: listData.params.pageSize,
    currentPage: listData.params.pageNum,
    total: listData.total
  }
})

const isSizeChanged = ref(false)

const onSizeChange = (pageSize: number) => {
  isSizeChanged.value = true
  getData({ pageSize, pageNum: 1 })
  setTimeout(() => {
    isSizeChanged.value = false
  }, 0)
}

const onCurrentChange = (pageNum: number) => !isSizeChanged.value && getData({ pageNum })

const onSearch = () => {
  listData.form = { ...form.value }
  getData({ pageNum: 1 })
}

const onReset = () => {
  form.value = { ...initForm.value }
  onSearch()
}

interface DeleteRowsAction {
  (): any | Promise<any>
}

interface DeleteRowsOptions {
  title?: string,
  desc?: string,
  action: DeleteRowsAction
}

const deleteRows = async (options: DeleteRowsAction | DeleteRowsOptions) => {
  if (isFunc(options)) {
    options = { action: options as DeleteRowsAction }
  }
  const { action, title, desc } = options as DeleteRowsOptions
  await ElMessageBox.confirm(
    desc || '确定删除选中的记录？',
    title || '删除',
    {
      type: 'warning',
      dangerouslyUseHTMLString: true
    }
  )
  await action()
  ElMessage.success('删除成功')
  await getData()
  const { params, data } = listData
  params.pageNum > 1 && data.length < 1 && getData({ pageNum: params.pageNum - 1 })
}

const tableVisible = ref(true)

watch(() => props.expandAll, async () => {
  tableVisible.value = false
  await nextTick()
  tableVisible.value = true
})

defineExpose({ setInitForm, getData, deleteRows, listData })
</script>

<template>
  <div :class="cls">
    <header :class="`${cls}_form`">
      <UiSearchForm :form-config="formProps" @reset="onReset" @search="onSearch">
        <slot name="form" :form="form" :onSearch="onSearch"></slot>
      </UiSearchForm>
    </header>
    <main :class="`${cls}_main`" v-loading="loading">
      <div :class="`${cls}_actions`">
        <slot name="actions" :form="form" :onSearch="onSearch" :selectedRows="selectedRows"></slot>
      </div>
      <div :class="`${cls}_data`">
        <slot>
          <ElTable v-if="tableVisible" v-bind="tableProps" @selection-change="onSelectionChange">
            <UiTableColumn v-for="column in allColumns" :key="column._k" :column="column" />
          </ElTable>
        </slot>
      </div>
      <footer v-if="config?.pager !== false" :class="`${cls}_pager`">
        <ElPagination v-bind="pagerProps" @size-change="onSizeChange" @current-change="onCurrentChange" />
      </footer>
    </main>
  </div>
</template>

<style lang="scss">
.ui-data-view {
  height: 100%;
  display: flex;
  flex-direction: column;
  &_main {
    flex: 1;
    height: 0;
    display: flex;
    flex-direction: column;
    padding: var(--page-margin);
    border-radius: 8px;
    background-color: var(--el-bg-color);
  }
  &_actions {
    margin-bottom: var(--page-margin);
  }
  &_data {
    flex: 1;
    height: 0;
  }
  &_pager {
    display: flex;
    justify-content: flex-end;
    margin-top: var(--page-margin);
    text-align: right;
  }
  .el-table {
    --el-table-header-bg-color: var(--el-bg-color-page);
    height: 100%;
  }
}
</style>
